unit QBDialogs;

interface

uses
  {$IFDEF VCL16}
    System.Classes,
    Vcl.Forms,
    Vcl.Controls,
  {$ELSE}
    Classes,
    Forms,
    Controls,
  {$ENDIF}
  QBStringTypes;

type
  TOnShowDialogEvent = TNotifyEvent;

function SMYesNoMsg(const MsgText: aqbString): Boolean;
function QBInputQuery(const ACaption, APrompt: aqbString; var Value: aqbString;
    APswdChar: Char = #0): Boolean;

var
  ShowDialogHandler: TOnShowDialogEvent;

implementation

uses
  {$IFDEF VCL16}
    Winapi.Windows,
    Vcl.Graphics,
    Vcl.ExtCtrls,
    Vcl.StdCtrls,
    System.SysUtils,
    System.Math,
    System.Types,
  {$ELSE}
    Windows,
    Graphics,
    ExtCtrls,
    StdCtrls,
    SysUtils,
    Math,
    Types,
  {$ENDIF}
  cxCheckBox,
  cxButtons,
  cxTextEdit,
  cxLabel,
  cxSpinEdit,
  cxClasses;

type
  TSMMsgDlgType = (smtWarning, smtError, smtInformation, smtConfirmation, smtCustom);
  TSMMsgDlgBtn = (smbYes, smbNo, smbOK, smbCancel, smbAbort, smbRetry, smbIgnore,
    smbAll, smbNoToAll, smbYesToAll, smbHelp);
  TSMMsgDlgButtons = set of TSMMsgDlgBtn;

  TMessageForm = class(TForm)
  private
    procedure HelpButtonClick(Sender: TObject);
  public
    constructor CreateNew(AOwner: TComponent); reintroduce;
  end;

const
  smbYesNo = [smbYes, smbNo];
  smbYesNoCancel = [smbYes, smbNo, smbCancel];
  smbOKCancel = [smbOK, smbCancel];
  smbAbortRetryIgnore = [smbAbort, smbRetry, smbIgnore];

resourcestring
  //  SOKButton = 'OK';
  //  SCancelButton = 'Cancel';
  //  SYesButton = '&Yes';
  //  SNoButton = '&No';
  //  SHelpButton = '&Help';
  //  SCloseButton = '&Close';
  //  SIgnoreButton = '&Ignore';
  //  SRetryButton = '&Repeat';
  //  SAbortButton = 'A&bort';
  //  SAllButton = '&All';

  SMsgDlgWarning = 'Warning';
  SMsgDlgError = 'Error';
  SMsgDlgInformation = 'Information';
  SMsgDlgConfirm = 'Question';
  SMsgDlgYes = '&Yes';
  SMsgDlgNo = '&No';
  SMsgDlgOK = 'OK';
  SMsgDlgCancel = 'Cancel';
  SMsgDlgHelp = '&Help';
  //  SMsgDlgHelpNone = 'Help file is not defined';
  //  SMsgDlgHelpHelp = 'Help';
  SMsgDlgAbort = 'A&bort';
  SMsgDlgRetry = '&Repeat';
  SMsgDlgIgnore = 'S&kip';
  SMsgDlgAll = '&All';
  SMsgDlgNoToAll = 'No &for all';
  SMsgDlgYesToAll = 'Yes for a&ll';

var
  Captions: array[TSMMsgDlgType] of Pointer = (@SMsgDlgWarning, @SMsgDlgError,
    @SMsgDlgInformation, @SMsgDlgConfirm, nil);
  IconIDs: array[TSMMsgDlgType] of PChar = (IDI_EXCLAMATION, IDI_HAND,
    IDI_ASTERISK, IDI_QUESTION, nil);
  ButtonNames: array[TSMMsgDlgBtn] of aqbString = (
    'Yes', 'No', 'OK', 'Cancel', 'Abort', 'Retry', 'Ignore', 'All', 'NoToAll',
    'YesToAll', 'Help');
  ButtonCaptions: array[TSMMsgDlgBtn] of Pointer = (
    @SMsgDlgYes, @SMsgDlgNo, @SMsgDlgOK, @SMsgDlgCancel, @SMsgDlgAbort,
    @SMsgDlgRetry, @SMsgDlgIgnore, @SMsgDlgAll, @SMsgDlgNoToAll, @SMsgDlgYesToAll,
    @SMsgDlgHelp);
  ModalResults: array[TSMMsgDlgBtn] of Integer = (
    mrYes, mrNo, mrOk, mrCancel, mrAbort, mrRetry, mrIgnore, mrAll, mrNoToAll,
    mrYesToAll, 0);

function GetAveCharSize(Canvas: TCanvas): TPoint;
var
  I: Integer;
  Buffer: array[0..51] of Char;
begin
  for I := 0 to 25 do
    Buffer[I] := Chr(I + Ord('A'));
  for I := 0 to 25 do
    Buffer[I + 26] := Chr(I + Ord('a'));
  GetTextExtentPoint(Canvas.Handle, Buffer, 52, TSize(Result));
  Result.X := Result.X div 52;
end;

var
  ButtonWidths: array[TSMMsgDlgBtn] of integer;

const
  mcHorzMargin = 8;
  mcVertMargin = 8;
  mcHorzSpacing = 10;
  mcVertSpacing = 10;
  mcButtonWidth = 55;
  mcButtonHeight = 14;
  mcButtonSpacing = 8;

constructor TMessageForm.CreateNew(AOwner: TComponent);
var
  NonClientMetrics: TNonClientMetrics;
begin
  inherited CreateNew(AOwner);
  NonClientMetrics.cbSize := sizeof(NonClientMetrics);
  if SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, @NonClientMetrics, 0) then
    Font.Handle := CreateFontIndirect(NonClientMetrics.lfMessageFont);
end;

procedure TMessageForm.HelpButtonClick(Sender: TObject);
begin
  Application.HelpContext(HelpContext);
end;

function SMCreateMessageDialog(const Msg: aqbString; DlgType: TSMMsgDlgType;
  Buttons: TSMMsgDlgButtons; const CheckCaption: aqbString): TForm;
var
  DialogUnits: TPoint;
  HorzMargin, VertMargin, HorzSpacing, VertSpacing, ButtonWidth,
    ButtonHeight, ButtonSpacing, ButtonCount, ButtonGroupWidth,
    IconTextWidth, IconTextHeight, X, ALeft, ACheckHeight,
    Index: Integer;
  B, DefaultButton, CancelButton: TSMMsgDlgBtn;
  IconID: PChar;
  TextRect, TmpRect: TRect;
  Ch: TcxCheckBox;
  Button: TcxButton;
  Image: TImage;
  ALabel: TLabel;
begin
  Result := TMessageForm.CreateNew(nil);
  Result.BiDiMode := Application.BiDiMode;
  Result.BorderStyle := bsDialog;
  Result.Canvas.Font := Result.Font;
  DialogUnits := GetAveCharSize(Result.Canvas);
  HorzMargin := MulDiv(mcHorzMargin, DialogUnits.X, 4);
  VertMargin := MulDiv(mcVertMargin, DialogUnits.Y, 8);
  HorzSpacing := MulDiv(mcHorzSpacing, DialogUnits.X, 4);
  VertSpacing := MulDiv(mcVertSpacing, DialogUnits.Y, 8);
  ButtonWidth := MulDiv(mcButtonWidth, DialogUnits.X, 4);
  for B := Low(TSMMsgDlgBtn) to High(TSMMsgDlgBtn) do
  begin
    if B in Buttons then
    begin
      if ButtonWidths[B] = 0 then
      begin
        TextRect := Rect(0, 0, 0, 0);
        QBDrawText(Result.Canvas.Handle,
          PaqbChar(aqbString(LoadResString(ButtonCaptions[B]))), -1,
          TextRect, DT_CALCRECT or DT_LEFT or DT_SINGLELINE or
          Result.DrawTextBiDiModeFlagsReadingOnly);
        ButtonWidths[B] := TextRect.Right - TextRect.Left + 8;
        if ButtonWidths[B] > ButtonWidth then
          ButtonWidth := ButtonWidths[B];
      end;
    end;
  end;
  ButtonHeight := MulDiv(mcButtonHeight, DialogUnits.Y, 8);
  ButtonSpacing := MulDiv(mcButtonSpacing, DialogUnits.X, 4);
  SetRect(TextRect, 0, 0, Screen.Width div 2, 0);
  QBDrawText(Result.Canvas.Handle, PaqbChar(Msg), Length(Msg) + 1*SizeOf(aqbChar), TextRect,
    DT_EXPANDTABS or DT_CALCRECT or DT_WORDBREAK or
    Result.DrawTextBiDiModeFlagsReadingOnly);
  IconID := IconIDs[DlgType];
  // ayz
  if CheckCaption <> '' then
  begin
    TmpRect := Rect(0, 0, 0, 0);
    QBDrawText(Result.Canvas.Handle, PaqbChar(CheckCaption),
      Length(CheckCaption) + 1, TmpRect, DT_EXPANDTABS or DT_CALCRECT or
      Result.DrawTextBiDiModeFlagsReadingOnly);
    TmpRect.Right := TmpRect.Right + 18;
    if TmpRect.Right > Screen.Width div 2 then
      TmpRect.Right := Screen.Width div 2;
    if TmpRect.Right > (TextRect.Right - TextRect.Left) then
      TextRect.Right := TextRect.Left + TmpRect.Right;
  end;
  // \ayz
  IconTextWidth := TextRect.Right;
  IconTextHeight := TextRect.Bottom;
  if IconID <> nil then
  begin
    Inc(IconTextWidth, 32 + HorzSpacing);
    if IconTextHeight < 32 then
      IconTextHeight := 32;
  end;
  ButtonCount := 0;
  for B := Low(TSMMsgDlgBtn) to High(TSMMsgDlgBtn) do
    if B in Buttons then
      Inc(ButtonCount);
  ButtonGroupWidth := 0;
  if ButtonCount <> 0 then
    ButtonGroupWidth := ButtonWidth * ButtonCount +
      ButtonSpacing * (ButtonCount - 1);
  Result.ClientWidth := Max(IconTextWidth, ButtonGroupWidth) + HorzMargin * 2;
  ACheckHeight := 0;
  if CheckCaption <> '' then
  begin
    Ch := TcxCheckBox.Create(Result);
    Ch.Name := 'chBox';
    Ch.Parent := Result;
    Ch.SetBounds(2 * HorzMargin + 32 + TextRect.Left,
      TextRect.Bottom + VertSpacing + 5,
      TextRect.Right,
      TextRect.Bottom);
    Ch.Properties.Caption := CheckCaption;
    ACheckHeight := 21;
    Ch.Height := ACheckHeight;
  end;
  Result.ClientHeight := Max(ACheckHeight + (TextRect.Bottom - TextRect.Top),
    IconTextHeight) + ButtonHeight + VertSpacing +
    VertMargin * 2;
  Result.Left := (Screen.Width div 2) - (Result.Width div 2);
  Result.Top := (Screen.Height div 2) - (Result.Height div 2);
  if DlgType <> smtCustom then
    Result.Caption := LoadResString(Captions[DlgType])
  else
    Result.Caption := Application.Title;
  if IconID <> nil then
  begin
    Image := TImage.Create(Result);
    Image.Parent := Result;
    Image.Name := 'Image';
    Image.Picture.Icon.Handle := LoadIcon(0, IconID);
    Image.SetBounds(HorzMargin, VertMargin, 32, 32);
  end;
  ALabel := TLabel.Create(Result);
  ALabel.Parent := Result;
  ALabel.Name := 'Message';
  ALabel.WordWrap := True;
  ALabel.Caption := Msg;
  ALabel.BoundsRect := TextRect;
  ALabel.BiDiMode := Result.BiDiMode;
  ALeft := IconTextWidth - TextRect.Right + HorzMargin;
  if ALabel.UseRightToLeftAlignment then
    ALeft := Result.ClientWidth - ALeft - ALabel.Width;
  ALabel.SetBounds(ALeft, VertMargin,
    TextRect.Right, TextRect.Bottom);
  if smbOk in Buttons then
    DefaultButton := smbOk
  else if smbYes in Buttons then
    DefaultButton := smbYes
  else
    DefaultButton := smbRetry;
  if smbCancel in Buttons then
    CancelButton := smbCancel
  else if smbNo in Buttons then
    CancelButton := smbNo
  else
    CancelButton := smbOk;
  X := (Result.ClientWidth - ButtonGroupWidth) div 2;
  Index := 0;
  for B := Low(TSMMsgDlgBtn) to High(TSMMsgDlgBtn) do
    if B in Buttons then
    begin
      Button := TcxButton.Create(Result);
      Button.Parent := Result;
      Button.Name := ButtonNames[B];
      Button.Caption := LoadResString(ButtonCaptions[B]);
      Button.ModalResult := ModalResults[B];
      if B = DefaultButton then
        Button.Default := True;
      if B = CancelButton then
        Button.Cancel := True;
      Button.TabOrder := Index;
      Inc(Index);
      Button.SetBounds(X,
        Max(ACheckHeight + (TextRect.Bottom - TextRect.Top), IconTextHeight)
        + VertMargin + VertSpacing, ButtonWidth, ButtonHeight);
      Inc(X, ButtonWidth + ButtonSpacing);
      if B = smbHelp then
        Button.OnClick := TMessageForm(Result).HelpButtonClick;
    end;
  Result.OnShow := ShowDialogHandler;
end;

function SMMessageDlgPosHelp(const Msg: aqbString; DlgType: TSMMsgDlgType;
  Buttons: TSMMsgDlgButtons; HelpCtx: Longint; X, Y: Integer;
  const HelpFileName: aqbString): Integer;
begin
  with SMCreateMessageDialog(Msg, DlgType, Buttons, '') do
  try
    HelpContext := HelpCtx;
    HelpFile := HelpFileName;
    if X >= 0 then
      Left := X;
    if Y >= 0 then
      Top := Y;
    if (Y < 0) and (X < 0) then
      Position := poScreenCenter;
    Result := ShowModal;
  finally
    Free;
  end;
end;

function SMMessageDlg(const Msg: aqbString; DlgType: TSMMsgDlgType;
  Buttons: TSMMsgDlgButtons; HelpCtx: Longint): Integer;
begin
  Result := SMMessageDlgPosHelp(Msg, DlgType, Buttons, HelpCtx, -1, -1, '');
end;

function SMYesNoMsg(const MsgText: aqbString): Boolean;
begin
  MessageBeep(MB_ICONQUESTION);
  Result := SMMessageDlg(MsgText, smtConfirmation, [smbYes, smbNo], 0) = mrYes;
end;

function SMInputString(const ACaption, APrompt: aqbString; IsPassword: Boolean;
    var Value: Variant; APswdChar: Char = #0; const InputType: Integer =
    {$IFDEF EC_UNICODE} vtWideString {$ELSE} vtString {$ENDIF} ): Boolean;
var
  Form: TForm;
  Prompt: TcxLabel;
  Edit: TcxCustomTextEdit;
  DialogUnits: TPoint;
  ButtonTop, ButtonWidth, ButtonHeight, IncWidth: Integer;
  Button: TcxButton;
  TextRect: TRect;
  Large: boolean;
begin
  Result := False;
  Form := TForm.Create(nil);
  try
    if Assigned(Application.MainForm) then
      Form.Font := Application.MainForm.Font;
    Form.Canvas.Font := Form.Font;
    DialogUnits := GetAveCharSize(Form.Canvas);
    Form.BorderStyle := bsDialog;
    Form.Caption := ACaption;
    Form.ClientWidth := MulDiv(180, DialogUnits.X, 4);
    Form.ClientHeight := MulDiv(57, DialogUnits.Y, 8);
    Form.Position := poScreenCenter;

    Prompt := TcxLabel.Create(Form);
    Prompt.Parent := Form;
    Prompt.AutoSize := True;
    Prompt.Left := MulDiv(4, DialogUnits.X, 4);
    Prompt.Top := MulDiv(4, DialogUnits.Y, 8);
    Prompt.Caption := APrompt;

    //igorp
    IncWidth := Form.ClientWidth;
    if (Form.Canvas.TextWidth(APrompt) < Screen.Width div 2) and (Pos(#13, APrompt) = 0) then
    begin
      if Form.ClientWidth < Form.Canvas.TextWidth(APrompt) then
        Form.ClientWidth := Form.Canvas.TextWidth(APrompt) + 10;
      Large := false;
    end else
    begin
      Large := true;
      Prompt.Properties.WordWrap := true;
      SetRect(TextRect, 0, 0, Screen.Width div 2, 0);
      QBDrawText(Form.Canvas.Handle, PaqbChar(APrompt), Length(APrompt) + 1*SizeOf(aqbChar), TextRect,
        DT_EXPANDTABS or DT_CALCRECT or DT_WORDBREAK or
      Form.DrawTextBiDiModeFlagsReadingOnly);
      TextRect.Top := TextRect.Top + 5;
      TextRect.Left := TextRect.Left + 10;
      TextRect.Right := TextRect.Right - 5;
      TextRect.Bottom := TextRect.Bottom + 5;
      Prompt.BoundsRect := TextRect;
      Form.ClientWidth := TextRect.Right + 5;
    end;
    IncWidth := Form.ClientWidth - IncWidth;
    //\igorp
    Edit := nil;
    case InputType of
      vtInteger: begin
        Edit := TcxSpinEdit.Create(Form);
        TcxSpinEdit(Edit).Value := Value;
      end;
      //pav
      {$IFDEF EC_UNICODE} vtWideString {$ELSE} vtString {$ENDIF}:
      //\pav fix CR29153
      begin
        Edit := TcxTextEdit.Create(Form);
        Edit.WideText := Value;
      end;
    end;
    Assert(Edit <> nil, '');
    Edit.Parent := Form;
    Edit.Left := Prompt.Left;
    if not Large then
      Edit.Top := MulDiv(15, DialogUnits.Y, 8)
    else
      Edit.Top := TextRect.Bottom + 10;
    Edit.Width := MulDiv(172, DialogUnits.X, 4) + IncWidth;
    Edit.Properties.MaxLength := 255;
    Edit.SelectAll();
    if (IsPassword) or (APswdChar <> #0) then
    begin
      Edit.Properties.EchoMode := eemPassword;
      Edit.Properties.PasswordChar := APswdChar;
    end;

    if not Large then
      ButtonTop := MulDiv(37, DialogUnits.Y, 8)
    else
      ButtonTop := TextRect.Bottom + Edit.Height + 20; // MulDiv(37, DialogUnits.Y, 8);
    ButtonWidth := MulDiv(50, DialogUnits.X, 4);
    ButtonHeight := MulDiv(14, DialogUnits.Y, 8);

    Form.ClientHeight := ButtonTop + ButtonHeight + 10;

    Button := TcxButton.Create(Form);
    Button.Parent := Form;
    Button.Caption := cxGetResourceStringNet(SMsgDlgOK);
    Button.ModalResult := mrOk;
    Button.Default := True;
    Button.SetBounds(MulDiv(38, DialogUnits.X, 4) + Round(IncWidth/2), ButtonTop, ButtonWidth,
      ButtonHeight);

    Button := TcxButton.Create(Form);
    Button.Parent := Form;
    Button.Caption := cxGetResourceStringNet(SMsgDlgCancel);
    Button.ModalResult := mrCancel;
    Button.Cancel := True;
    Button.SetBounds(MulDiv(92, DialogUnits.X, 4) + Round(IncWidth/2), ButtonTop, ButtonWidth,
      ButtonHeight);

    Form.OnShow := ShowDialogHandler;
    if Form.ShowModal() = mrOk then begin
      Value := Edit.WideText;
      Result := True;
    end;
  finally
    Form.Free();
  end;
end;

function QBInputQuery(const ACaption, APrompt: aqbString; var Value: aqbString;
    APswdChar: Char = #0): Boolean;
var
  V: Variant;
begin
  V := Value;
  Result := SMInputString(ACaption, APrompt, False, V, APswdChar);
  if Result then
    Value := V;
end;

initialization
  ShowDialogHandler := nil;

end.
